package mlog

import (
	"errors"
	"fmt"
	"log"
	"os"
	"sync"
	"time"
)

type Handler interface {
	Log(r *Record)
	Reload() error
	Close()
}

type FileHandler struct {
	filename  string
	filter    []Filter
	driver    *log.Logger
	fh        *os.File
	showLevel bool
	flag      int
}

func NewFileHandler(file string, fmt int) (h *FileHandler) {
	h = &FileHandler{
		filename: file,
	}

	if fmt == 0 {
		h.flag = log.LstdFlags
	} else {
		h.flag = fmt
	}

	if err := h.init(); nil != err {
		panic(err)
	}

	return
}

func NewBareHandler(file string) (h *FileHandler) {
	h = NewFileHandler(file, 0)

	return
}

func NewLevelHandler(file string, format int, level ...string) (h *FileHandler) {
	h = NewFileHandler(file, format)
	h.AddFilter(LevelFilter(level...))
	h.showLevel = true
	return
}

func (h *FileHandler) AddFilter(f ...Filter) {
	h.filter = append(h.filter, f...)
}

func (h *FileHandler) setFormatFlag(fmt int) {
	h.flag = fmt
	h.driver.SetFlags(fmt)
}

func (h *FileHandler) Log(r *Record) {
	for _, f := range h.filter {
		if !f(r) {
			return
		}
	}

	var (
		v      = r.args
		format = r.format
	)

	if h.showLevel {
		v = append([]interface{}{r.level.String()}, v...)
		if len(format) > 0 {
			format = "%s " + r.format
		}
	}

	if len(format) > 0 {
		h.driver.Printf(format, v...)
	} else {
		h.driver.Println(v...)
	}
}

func (h *FileHandler) Reload() (err error) {
	if nil != h.fh {
		if err = h.fh.Close(); nil != err {
			h.fh = nil
		}
	}

	return h.init()
}

func (h *FileHandler) Close() {
	if nil != h.fh {
		_ = h.fh.Close()
	}
}

func (h *FileHandler) init() (err error) {
	if len(h.filename) > 0 {
		if h.fh, err = os.OpenFile(h.filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0664); nil != err {
			log.Fatalf("logger: error open %s, %s", h.filename, err)
			return
		}
	}

	if nil != h.fh {
		h.driver = log.New(h.fh, "", h.flag)
	} else {
		h.driver = log.New(os.Stdout, "", h.flag)
	}

	return
}

type RotateFileHandler struct {
	FileHandler
	file       string
	suffix     string
	rotateUnit uint8
}

func NewRotateFileHandler(file string, format int, rotateUnit uint8, level ...string) (h *RotateFileHandler) {
	h = &RotateFileHandler{file: file, rotateUnit: rotateUnit}
	h.suffix = h.getSuffix()
	fh := NewLevelHandler(fmt.Sprintf("%s.%s", file, h.suffix), format, level...)
	h.FileHandler = *fh
	return
}

func (h *RotateFileHandler) getSuffix() string {
	var (
		now    = time.Now()
		suffix string
	)

	if h.rotateUnit == No {
		return h.file
	}

	if h.rotateUnit == Secondly {
		secSuffix := (now.Second() - now.Second()/3600*3600) % 5
		//log.Printf("current sec: %d, suffix: %d", now.Second(), secSuffix)
		suffix = fmt.Sprintf("%d-%02d-%02d-%02d%d", now.Year(), now.Month(), now.Day(), now.Hour(), secSuffix)
	} else if h.rotateUnit == Hourly {
		suffix = now.Format("2006-01-02-15")
	} else if h.rotateUnit == Daily {
		suffix = now.Format("2006-01-02")
	} else if h.rotateUnit == Monthly {
		suffix = now.Format("2006-01")
	} else if h.rotateUnit == Yearly {
		suffix = now.Format("2006")
	} else {
		panic(errors.New(fmt.Sprintf("unexpected rotate unit: %d", h.rotateUnit)))
	}
	return suffix
}

func (h *RotateFileHandler) Log(r *Record) {
	suffix := h.getSuffix()
	if h.rotateUnit > No && suffix != h.suffix {
		h.suffix = suffix
		h.filename = fmt.Sprintf("%s.%s", h.file, suffix)
		//log.Printf("trigger reload")
		_ = h.Reload()
	}

	h.FileHandler.Log(r)
}

type JsonHandler struct {
	Handler
}

func NewJsonHandler(handler Handler) (h *JsonHandler) {
	h = &JsonHandler{handler}

	return
}

func (h *JsonHandler) Log(r *Record) {
	h.Handler.Log(r.Json())
}

type SmartHandler struct {
	Handler
	ch chan *Record
	wg *sync.WaitGroup
}

func NewSmartHandler(handler Handler) (h *SmartHandler) {
	h = &SmartHandler{
		Handler: handler,
		ch:      make(chan *Record),
		wg:      new(sync.WaitGroup),
	}

	h.wg.Add(1)
	go h.consumer()

	return
}

func (h *SmartHandler) consumer() {
	defer h.wg.Done()
	//defer h.Handler.Log(&Record{level: Debug, args: Args{"log consumer stoped"}})
	//h.Handler.Log(&Record{level: Debug, args: Args{"log consumer started"}})
	for r := range h.ch {
		h.Handler.Log(r)
	}
}

func (h *SmartHandler) Log(r *Record) {
	h.ch <- r
}

func (h *SmartHandler) Close() {
	defer h.Handler.Close()

	close(h.ch)
	h.wg.Wait()
}
